﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Web.Mvc;
using OpenFlashChart;
using Peak.ApplicationServices.Services;
using Peak.Core;
using MvcContrib;
using Peak.Web.Controllers.Models;
using Peak.Web.Controllers.Services;
using SharpArch.Data.NHibernate;
using SharpArch.Web.NHibernate;
using Peak.Resources;

namespace Peak.Web.Controllers
{
  public class SprintController : BaseController
  {
    static readonly log4net.ILog _log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

    public const int BurndownHeight = 200;
    private readonly ISprintService _sprintService;
    private readonly IStoryService _storyService;
    
    public SprintController(ISessionService sessionService, ISprintService sprintService, IStoryService storyService)
      : base(sessionService)
    {
      _sprintService = sprintService;
      _storyService = storyService;
    }

    public ActionResult Current()
    {
      if (!_sessionService.IsProjectSelected || _sessionService.Project.CurrentSprint == null)
      {
        return MessageBox(Messages.NoCurrentSprint);
      }
      else
      {
        return this.RedirectToAction(f => f.Index(_sessionService.Project.CurrentSprint.Id));
      }
    }

    public ActionResult Index(int id)
    {
      return View(_sprintService.Get(id));
    }    

    public ActionResult New()
    {
      if (_sessionService.Project.CurrentSprint != null)
        return MessageBox(ValidationErrors.OnlyOneOngoingSprint);

      return View(new SprintModel(){Start = DateTime.Today, End = DateTime.Today.AddDays(15)});
    }

    [AcceptVerbs(HttpVerbs.Post)]
    [ValidateInput(false)]
    [Transaction]
    public ActionResult New(SprintModel sprint)
    {
      if (!sprint.IsValid())
      {
        return View(sprint);
      }

      Sprint dbSprint = new Sprint();
      sprint.Update(dbSprint);
      dbSprint.Created = DateTime.Now;
      dbSprint.CreatedBy = Thread.CurrentPrincipal.Identity.Name;
      _sessionService.Project.AddSprint(dbSprint);

      return Back();
    }

    public ActionResult Edit(int sprintId)
    {
      Sprint sprint = _sprintService.Get(sprintId);      
      return View(new SprintModel(sprint));
    }

    [AcceptVerbs(HttpVerbs.Post)]
    [ValidateInput(false)]
    [Transaction]
    public ActionResult Edit(SprintModel sprint)
    {
      Sprint dbSprint = _sprintService.Get(sprint.SprintId);
      try
      {
        sprint.Update(dbSprint);
      }
      catch (OnlyOneOngoingSprintException)
      {
        NHibernateSession.Current.Transaction.Rollback();
        ModelState.AddModelError<SprintModel>(f => f.Status, ValidationErrors.OnlyOneOngoingSprint);
        return View(sprint);
      }
      catch (NotEstimatedException)
      {
        NHibernateSession.Current.Transaction.Rollback();
        ModelState.AddModelError<SprintModel>(f => f.Status, ValidationErrors.OneOrMoreStoryIsNotEstimated);
        return View(sprint);
      }
      catch (NotFinishedStoriesException)
      {
        NHibernateSession.Current.Transaction.Rollback();
        ModelState.AddModelError<SprintModel>(f => f.Status, ValidationErrors.NotFinishedStories);
        return View(sprint);
      }
      if (!dbSprint.IsValid())
      {
        return View(sprint);
      }

      _sprintService.Update(dbSprint);

      return Back();
    }

    public ActionResult SelectStories(int sprintId)
    {
      StoryFilterModel storyFilterModel = new StoryFilterModel()
                                            {
                                              PageSize = null,
                                              SortDirection = SortDirection.Desc,
                                              SortOrder = StorySortOrder.Priority,
                                              StatusDone = false,
                                              StatusInProgress = true,
                                              StatusToDo = true,
                                              StatusVerify = true,
                                              AssignedToSprint = false
                                            };
      string returnUrl = BuildUrlFromExpression<SprintController>(f=>f.SelectStoriesOk(sprintId,null));
      return ControllerExtensions2.RedirectToAction2<SelectStoriesController>(f => f.Index(sprintId,returnUrl, true, storyFilterModel));
    }
     
    [Transaction]
    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult SelectStoriesOk(int sprintId, int[] storyIds)
    {
      Sprint sprint = _sprintService.Get(sprintId);
      foreach (int id in storyIds)
      {
        if(sprint.SprintStories.FirstOrDefault(f=>f.Story.Id == id) == null)
        {
          sprint.AddStory(_storyService.Get(id));
        }
      }
      return this.RedirectToAction(f => f.Index(sprintId));
    }

    public ActionResult Burndown(int sprintId)
    {
      Sprint sprint = _sprintService.Get(sprintId);
      OpenFlashChart.OpenFlashChart chart = new OpenFlashChart.OpenFlashChart();
      chart.Title = new Title("Burndown");
      chart.Bgcolor = "#E7E9DC";
      chart.X_Axis.GridColour = "BBBEAB";
      chart.Y_Axis.GridColour = "BBBEAB";
      chart.X_Axis.Colour = chart.Y_Axis.Colour = "172028";
      chart.X_Axis.Offset = false;

      Project project = _sessionService.Project;

      List<string> labels = new List<string>();
      DateTime startDate = sprint.StartDate;      
      Dictionary<DateTime, int> date2Number = new Dictionary<DateTime, int>();
      int day = 0;
      for (DateTime date = startDate; date <= sprint.EndDate.Date; date = date.AddDays(1))
      {        
        if (project.Calendar.IsWorkingDay(date))
        {
          labels.Add("            "+date.Day.ToString());
          date2Number[date] = day;
          day++;
        }
      }
      labels.Add("");
      date2Number.Add(sprint.EndDate.Date.AddDays(1), day);

      chart.X_Axis.SetLabels(labels);
      chart.X_Axis.SetRange(0, labels.Count - 1);

      if (sprint.Status == SprintStatus.Planning || sprint.Burndown.Count == 0)
      {
        return new ChartDataResult(chart);
      }

      AddStoriesChart(chart, date2Number,sprint);

      AddBurndown(chart, project, startDate, sprint);

      AddIdealBurndown(chart, labels.Count, sprint);                     
      
      return new ChartDataResult(chart); 
    }

    private void AddBurndown(OpenFlashChart.OpenFlashChart chart, Project project, DateTime startDate, Sprint sprint)
    {
      List<double> data = new List<double>();
      double max = 0;      
      DateTime endDate = DateTime.Now.Date;
      if (endDate > sprint.EndDate.Date)
        endDate = sprint.EndDate.Date;

      data.Add(sprint.InitialEffort.TotalHours); 

      for (DateTime date = startDate; date <= endDate; date = date.AddDays(1))
      {
        if (project.Calendar.IsWorkingDay(date))
        {
          TimeSpan effort = sprint.GetBurndownEffort(date);
          if (effort.TotalHours > max)
            max = effort.TotalHours;
          data.Add(effort.TotalHours);          
        }
      }
      Line line1 = new Line();
      line1.Values = data;
      line1.Colour = "#005000";
      line1.Text = Labels.HoursRemaining;
      line1.DotSize = 5;
      line1.AttachToRightAxis(true);
      chart.AddElement(line1);
      chart.Y_Axis_Right = new YAxis();
      chart.Y_Axis_Right.SetRange(0, max, (int)(30 * (max) / BurndownHeight));
    }

    private void AddIdealBurndown(OpenFlashChart.OpenFlashChart chart, int daysCount, Sprint sprint)
    {
      double startEffort = sprint.InitialEffort.TotalHours; 
      List<double> idealBurnDownValues = new List<double>();
      idealBurnDownValues.Add(startEffort);
      for (int i = 1; i < daysCount - 1; i++)
      {
        idealBurnDownValues.Add(Math.Round((daysCount - i - 1) * startEffort / (daysCount - 1), 1));
      }
      idealBurnDownValues.Add(0);

      Line idealBurnDown = new Line();
      idealBurnDown.Colour = "#FF0000";
      idealBurnDown.Text = Labels.IdealBurndown;
      idealBurnDown.DotSize = 5;
      idealBurnDown.Values = idealBurnDownValues;
      idealBurnDown.AttachToRightAxis(true);
      chart.AddElement(idealBurnDown);
    }

    private void AddStoriesChart(OpenFlashChart.OpenFlashChart chart, Dictionary<DateTime,int> date2Number, Sprint sprint)
    {
      try
      {
        HBar stories = new HBar();
        stories.Text = Labels.Stories;

        for (int i = 0; i < sprint.SprintStories.Count; i++)
        {
          if (sprint.SprintStories[i].StartDate != null &&
              date2Number.ContainsKey(sprint.SprintStories[i].StartDate.Value.Date))
          {
            int start = date2Number[sprint.SprintStories[i].StartDate.Value.Date];
            int end;
            if (sprint.SprintStories[i].EndDate != null &&
                sprint.SprintStories[i].EndDate.Value < DateTime.Now.Date)
            {
              end = date2Number[sprint.SprintStories[i].EndDate.Value.Date] + 1;
            }
            else
            {
              if (sprint.EndDate.Date < DateTime.Now.Date)
              {
                end = date2Number[sprint.EndDate.Date] + 1;
              }
              else
              {
                end = date2Number[DateTime.Now.Date] + 1;
              }
            }
            stories.Add(new HBarValue(start, end, sprint.SprintStories[i].Story.Name));
          }
          else
          {
            stories.Add(new HBarValue(0, 0));
          }
        }
        stories.Colour = "#2A343D";
        chart.Tooltip = new ToolTip("_default");
        chart.Tooltip.MouseStyle = ToolTipStyle.NORMAL;
        chart.Y_Axis.Min = 1;
        chart.Y_Axis.Max = stories.Values.Count;
        chart.Y_Axis.Offset = true;
        chart.Y_Axis.SetLabels(new string[] {});
        chart.AddElement(stories);
      }
      catch(Exception e)
      {
        if(_log.IsErrorEnabled)
        {
          _log.Error("AddStoriesChart error",e);
        }
      }
    }

    [AcceptVerbs(HttpVerbs.Post)]
    [Transaction]
    public ActionResult RemoveStories(int sprintId,int[] ids)
    {
      Sprint sprint = _sprintService.Get(sprintId);
      foreach (int id in ids)
      {
        sprint.RemoveStory(_storyService.Get(id));        
      }
      return this.RedirectToAction(f => f.Index(sprintId));
    }
  }
}
